<# .SYNOPSIS Configure Network Security security options using positional arguments - Secpol Policy Framework. .SCRIPTTYPE Computer Configuration .DESCRIPTION This script applies Network Security security option settings using positional arguments based on the PolicyDatabase array. Policies configured (18 total): 1. Allow Local System to use computer identity for NTLM - 0=Disabled, 1=Enabled 2. Allow LocalSystem NULL session fallback - 0=Disabled, 1=Enabled 3. Allow PKU2U authentication requests using online identities - 0=Disabled, 1=Enabled 4. Configure encryption types allowed for Kerberos - Bitmask value: - 0x1 (1) = DES_CBC_CRC - 0x2 (2) = DES_CBC_MD5 - 0x4 (4) = RC4_HMAC_MD5 - 0x8 (8) = AES128_HMAC_SHA1 - 0x10 (16) = AES256_HMAC_SHA1 - 0x20 (32) = Future encryption types Example: 0x7FFFFFFF (2147483640) = All types, 0x18 (24) = AES128 + AES256 only 5. Do not store LAN Manager hash value on next password change - 0=Store, 1=Do not store 6. Force logoff when logon hours expire - 0=Disabled, 1=Enabled 7. LAN Manager authentication level: - 0 = Send LM and NTLM responses - 1 = Send LM and NTLM, use NTLMv2 session security if negotiated - 2 = Send NTLM response only - 3 = Send NTLMv2 response only - 4 = Send NTLMv2 response only, refuse LM - 5 = Send NTLMv2 response only, refuse LM and NTLM (Most secure) 8. LDAP client encryption requirements: - 0 = None (no encryption required) - 1 = Negotiate encryption - 2 = Require encryption 9. LDAP client signing requirements: - 0 = None (no signing required) - 1 = Negotiate signing - 2 = Require signing 10. Minimum session security for NTLM SSP clients - Bitmask value: - 0x00000000 (0) = No minimum security - 0x00080000 (524288) = Require NTLMv2 session security - 0x20000000 (536870912) = Require 128-bit encryption - 0x20080000 (537395200) = Require NTLMv2 + 128-bit encryption (Recommended) 11. Minimum session security for NTLM SSP servers - Bitmask value: - 0x00000000 (0) = No minimum security - 0x00080000 (524288) = Require NTLMv2 session security - 0x20000000 (536870912) = Require 128-bit encryption - 0x20080000 (537395200) = Require NTLMv2 + 128-bit encryption (Recommended) 12. Restrict NTLM: Add remote server exceptions - Multi-string list (comma-separated) Example: "server1.domain.com,server2.domain.com" or "" for none 13. Restrict NTLM: Add server exceptions in this domain - Multi-string list (comma-separated) Example: "DC01,DC02,FileServer" or "" for none 14. Restrict NTLM: Audit Incoming NTLM Traffic: - 0 = Disable - 1 = Enable auditing for domain accounts - 2 = Enable auditing for all accounts 15. Restrict NTLM: Audit NTLM authentication in this domain: - 0 = Disable - 1 = Enable for domain accounts to domain servers - 3 = Enable for domain accounts - 5 = Enable for domain servers - 7 = Enable for all 16. Restrict NTLM: Incoming NTLM traffic: - 0 = Allow all - 1 = Deny for domain accounts - 2 = Deny all 17. Restrict NTLM: NTLM authentication in this domain: - 0 = Disable - 1 = Deny for domain accounts to domain servers - 3 = Deny for domain accounts - 5 = Deny for domain servers - 7 = Deny all 18. Restrict NTLM: Outgoing NTLM traffic to remote servers: - 0 = Allow all - 1 = Audit all - 2 = Deny all NOTE: These settings control network authentication protocols and security. LAN Manager and NTLM settings affect authentication compatibility and security. Test thoroughly before implementing restrictive NTLM policies. .PARAMETER PolicyValues JSON array string containing 18 policy values (in order). Use empty strings "" to skip policies. Format: '["value1","value2",...,"value18"]' .PARAMETER LogLevel Logging verbosity: Silent, Normal, Verbose, Debug .PARAMETER LogPath Custom log file path (optional) .PARAMETER WhatIf Preview changes without applying them .EXAMPLE .\Set-NetworkSecuritySecurityOptions.ps1 '["1","0","0","24","1","1","5","2","2","537395200","537395200","","","2","0","0","0","2"]' Configures all 18 network security policies with secure settings .EXAMPLE .\Set-NetworkSecuritySecurityOptions.ps1 '["1","","","24","1","","5","2","","","","","","","","","",""]' -WhatIf Preview changes for selected authentication and encryption policies #> param( [Parameter(Position=0, ValueFromRemainingArguments=$true)] [string[]]$PolicyValuesArray = @("[]"), [ValidateSet('Silent','Normal','Verbose','Debug')] [string]$LogLevel = 'Normal', [string]$LogPath = $null, [switch]$WhatIf ) # Combine all arguments into a single PolicyValues string # First, try to get the original command line with proper quotes $PolicyValues = $null try { $currentPID = $PID Write-Host "Current Process ID: $currentPID" -ForegroundColor Cyan $process = Get-CimInstance Win32_Process -Filter "ProcessId = $currentPID" if ($process) { $commandLine = $process.CommandLine Write-Host "Full command line: $commandLine" -ForegroundColor Yellow if ($commandLine) { $scriptName = [System.IO.Path]::GetFileName($MyInvocation.MyCommand.Path) $escapedScriptName = [regex]::Escape($scriptName) $pattern = "-File\s+`"[^`"]*\\$escapedScriptName`"\s+(.+?)(?:\s+(?:-LogLevel|-LogPath|-WhatIf)|$)" Write-Host "Using regex pattern: $pattern" -ForegroundColor DarkGray if ($commandLine -match $pattern) { $rawArgument = $matches[1].Trim() Write-Host "Raw argument extracted: $rawArgument" -ForegroundColor Magenta if ($rawArgument -match '^"(.*)"$') { $PolicyValues = $matches[1] } else { $PolicyValues = $rawArgument } Write-Host "Extracted PolicyValues from command line: $PolicyValues" -ForegroundColor Green } else { Write-Host "Command line regex did not match. Command line: $commandLine" -ForegroundColor Red } } else { Write-Host "CommandLine property is null or empty" -ForegroundColor Red } } else { Write-Host "Failed to get process information for PID $currentPID" -ForegroundColor Red } } catch { Write-Host "Error extracting from command line: $($_.Exception.Message)" -ForegroundColor Red Write-Verbose "Could not extract from command line: $($_.Exception.Message)" } if (-not $PolicyValues) { $PolicyValues = if ($PolicyValuesArray.Count -gt 1) { $PolicyValuesArray -join '' } else { $PolicyValuesArray[0] } Write-Verbose "Using parameter-based PolicyValues: $PolicyValues" } # Input DB - Network Security Security Options # RegistryType: 4=DWORD, 1=String, 7=MultiString, 3=Binary $PolicyDatabase = @( @{ Name = "Network security: Allow Local System to use computer identity for NTLM" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Control\Lsa\UseMachineId" RegistryType = 4 }, @{ Name = "Network security: Allow LocalSystem NULL session fallback" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\allownullsessionfallback" RegistryType = 4 }, @{ Name = "Network security: Allow PKU2U authentication requests to this computer to use online identities" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Control\Lsa\pku2u\AllowOnlineID" RegistryType = 4 }, @{ Name = "Network security: Configure encryption types allowed for Kerberos" KeyGroup = "[Registry Values]" Key = "MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters\SupportedEncryptionTypes" RegistryType = 4 }, @{ Name = "Network security: Do not store LAN Manager hash value on next password change" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Control\Lsa\NoLMHash" RegistryType = 4 }, @{ Name = "Network security: Force logoff when logon hours expire" KeyGroup = "[System Access]" Key = "ForceLogoffWhenHourExpire" RegistryType = 4 }, @{ Name = "Network security: LAN Manager authentication level" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Control\Lsa\LmCompatibilityLevel" RegistryType = 4 }, @{ Name = "Network security: LDAP client encryption requirements" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Services\ldap\LDAPClientEncryption" RegistryType = 4 }, @{ Name = "Network security: LDAP client signing requirements" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Services\ldap\LDAPClientIntegrity" RegistryType = 4 }, @{ Name = "Network security: Minimum session security for NTLM SSP based (including secure RPC) clients" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\NTLMMinClientSec" RegistryType = 4 }, @{ Name = "Network security: Minimum session security for NTLM SSP based (including secure RPC) servers" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\NTLMMinServerSec" RegistryType = 4 }, @{ Name = "Network security: Restrict NTLM: Add remote server exceptions for NTLM authentication" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\ClientAllowedNTLMServers" RegistryType = 7 }, @{ Name = "Network security: Restrict NTLM: Add server exceptions in this domain" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\DCAllowedNTLMServers" RegistryType = 7 }, @{ Name = "Network security: Restrict NTLM: Audit Incoming NTLM Traffic" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\AuditReceivingNTLMTraffic" RegistryType = 4 }, @{ Name = "Network security: Restrict NTLM: Audit NTLM authentication in this domain" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\AuditNTLMInDomain" RegistryType = 4 }, @{ Name = "Network security: Restrict NTLM: Incoming NTLM traffic" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\RestrictReceivingNTLMTraffic" RegistryType = 4 }, @{ Name = "Network security: Restrict NTLM: NTLM authentication in this domain" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\RestrictNTLMInDomain" RegistryType = 4 }, @{ Name = "Network security: Restrict NTLM: Outgoing NTLM traffic to remote servers" KeyGroup = "[Registry Values]" Key = "MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\RestrictSendingNTLMTraffic" RegistryType = 4 } ) # Script-wide variables $script:LogFile = $null $script:StartTime = Get-Date $script:ProcessedCount = 0 $script:SuccessCount = 0 $script:FailureCount = 0 $script:SkippedCount = 0 # Initialize logging function Initialize-LogPath { if ($LogPath) { $logDir = Split-Path $LogPath -Parent if ($logDir -and -not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } return $LogPath } # Try to get agent directory, fallback to script directory $baseDir = $PSScriptRoot try { $registryPath = if ([Environment]::Is64BitOperatingSystem) { "HKLM:\SOFTWARE\WOW6432Node\AdventNet\DesktopCentral\DCAgent" } else { "HKLM:\SOFTWARE\AdventNet\DesktopCentral\DCAgent" } if (Test-Path $registryPath) { $agentDir = (Get-ItemProperty -Path $registryPath -Name "DCAgentInstallDir" -ErrorAction SilentlyContinue).DCAgentInstallDir if ($agentDir -and (Test-Path $agentDir)) { $baseDir = $agentDir } } } catch { # Silently fall back to script directory } $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss' $logDir = Join-Path (Join-Path $baseDir "logs") "SecurityPolicies" if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } return Join-Path $logDir "NetworkSecuritySecurityOptions_$timestamp.log" } # Logging function with log levels function Write-Log { param( [string]$Message, [ValidateSet('INFO','SUCCESS','WARNING','ERROR','DEBUG','PROGRESS')] [string]$Level = 'INFO', [string]$Component = 'Main' ) $levelPriority = @{ 'Silent' = 0 'Normal' = 1 'Verbose' = 2 'Debug' = 3 } $messagePriority = @{ 'ERROR' = 0 'WARNING' = 0 'SUCCESS' = 1 'PROGRESS' = 1 'INFO' = 2 'DEBUG' = 3 } $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $logEntry = "[$timestamp] [$Level] [$Component] $Message" # Always write to log file if ($script:LogFile) { Add-Content -Path $script:LogFile -Value $logEntry -ErrorAction SilentlyContinue } # Console output based on log level if ($levelPriority[$LogLevel] -ge $messagePriority[$Level]) { switch ($Level) { 'ERROR' { Write-Host $logEntry -ForegroundColor Red } 'WARNING' { Write-Host $logEntry -ForegroundColor Yellow } 'SUCCESS' { Write-Host $logEntry -ForegroundColor Green } 'DEBUG' { Write-Host $logEntry -ForegroundColor Cyan } 'PROGRESS'{ Write-Host $logEntry -ForegroundColor Magenta } default { Write-Host $logEntry } } } } # Progress logging function function Write-ProgressLog { param([string]$Message, [string]$Component = 'Progress') Write-Log -Message $Message -Level 'PROGRESS' -Component $Component } # Check if running as administrator function Test-Admin { $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($currentUser) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } # Initialize script function Initialize-Script { $script:LogFile = Initialize-LogPath Write-Log "========================================" -Level 'INFO' Write-Log "Network Security Security Options Configuration Started" -Level 'INFO' Write-Log "Script: $($MyInvocation.ScriptName)" -Level 'INFO' Write-Log "Log Level: $LogLevel" -Level 'INFO' Write-Log "WhatIf Mode: $WhatIf" -Level 'INFO' Write-Log "========================================" -Level 'INFO' if (-not (Test-Admin)) { Write-Log "ERROR: This script requires administrator privileges" -Level 'ERROR' exit 1 } } # Import INF file into structured data function Import-InfFile { param([string]$Path) if (-not (Test-Path $Path)) { Write-Log "INF file not found: $Path" -Level 'ERROR' return $null } $infData = @{} $currentSection = $null Get-Content $Path -Encoding Unicode | ForEach-Object { $line = $_.Trim() # Skip empty lines and comments if ([string]::IsNullOrWhiteSpace($line) -or $line.StartsWith(';')) { return } # Section header if ($line -match '^\[(.+)\]$') { $currentSection = $matches[1] if (-not $infData.ContainsKey($currentSection)) { $infData[$currentSection] = @{} } return } # Key-value pair if ($currentSection -and $line -match '^(.+?)\s*=\s*(.*)$') { $key = $matches[1].Trim() $value = $matches[2].Trim() $infData[$currentSection][$key] = $value } } return $infData } # Write INF data back to file function Write-InfFile { param( [hashtable]$Data, [string]$Path ) $output = @() $output += "[Unicode]" $output += "Unicode=yes" # Ensure [Version] section is first if ($Data.ContainsKey('Version')) { $output += "[Version]" foreach ($key in $Data['Version'].Keys) { $output += "$key=$($Data['Version'][$key])" } } # Write other sections foreach ($section in ($Data.Keys | Where-Object { $_ -notin @('Unicode','Version') } | Sort-Object)) { $output += "[$section]" foreach ($key in ($Data[$section].Keys | Sort-Object)) { $output += "$key=$($Data[$section][$key])" } } $output | Out-File -FilePath $Path -Encoding unicode -Force } # Set individual secpol row function Set-SecpolRow { param( [string]$Name, [string]$KeyGroup, [string]$Key, [string]$Value, [int]$RegistryType, [hashtable]$PolicyData ) if ([string]::IsNullOrWhiteSpace($Value)) { Write-Log "Skipping '$Name' - No value provided" -Level 'WARNING' -Component $Name $script:SkippedCount++ return $false } # Special handling for Kerberos encryption types - convert comma-separated values to bitmask if ($Key -like "*SupportedEncryptionTypes") { if ($Value -match ',') { # Input contains commas - treat as individual encryption type values to OR together $encryptionTypes = $Value -split ',' | Where-Object { $_ -match '^\d+$' } | ForEach-Object { [int]$_.Trim() } if ($encryptionTypes.Count -gt 0) { $bitmask = 0 foreach ($type in $encryptionTypes) { $bitmask = $bitmask -bor $type } $originalValue = $Value $Value = $bitmask.ToString() Write-Log "Converted encryption types '$originalValue' to bitmask: $Value" -Level 'DEBUG' -Component $Name } } } # Special handling for string values (RegistryType = 1 or 7): Convert newlines to commas for INF format compatibility # ONLY if the value doesn't already contain commas (which indicates it's already properly formatted) # Skip this for Kerberos encryption types as we just processed those commas above if (($RegistryType -eq 1 -or $RegistryType -eq 7) -and $Key -notlike "*SupportedEncryptionTypes") { if ($Value -match '(\r\n|\n|\r)') { # Replace various newline formats with commas only if no commas exist $Value = $Value -replace '(\r\n|\n|\r)', ',' # Remove any trailing commas $Value = $Value.TrimEnd(',') Write-Log "Converted multi-line text to comma-separated format for INF compatibility" -Level 'DEBUG' -Component $Name } } Write-ProgressLog "Processing: $Name" -Component $Name Write-Log "Setting: $Name = $Value" -Level 'INFO' -Component $Name Write-Log "Location: $KeyGroup\$Key" -Level 'DEBUG' -Component $Name if ($WhatIf) { Write-Log "[WhatIf] Would set $KeyGroup\$Key = $Value" -Level 'INFO' -Component $Name $script:SuccessCount++ return $true } try { # Update policy data with proper registry type format $sectionName = $KeyGroup.Trim('[',']') if (-not $PolicyData.ContainsKey($sectionName)) { $PolicyData[$sectionName] = @{} } if($sectionName -eq 'Registry Values' ) { # Format: key=RegistryType,Value (e.g., key=4,1 for DWORD with value 1) $PolicyData[$sectionName][$Key] = "$RegistryType,$Value" }else{ $PolicyData[$sectionName][$Key] = $Value } Write-Log "Successfully configured: $Name" -Level 'SUCCESS' -Component $Name $script:SuccessCount++ return $true } catch { Write-Log "Failed to configure '$Name': $($_.Exception.Message)" -Level 'ERROR' -Component $Name $script:FailureCount++ return $false } } # Save all changes to security policy # Parse PolicyValues array string to array function Parse-PolicyValuesArray { param([string]$ArrayString) # Check if it looks like JSON format if ($ArrayString -match '^\s*\[.*\]\s*$') { try { # Fix common JSON format issues: unquoted alphanumeric values $FixedArrayString = $ArrayString -replace '\[(\w+)(,|])', '["$1"$2' -replace ',(\w+)(,|])', ',"$1"$2' # Parse the JSON-like array string $Arguments = ConvertFrom-Json $FixedArrayString Write-Log "Successfully parsed policy values array: $($Arguments.Count) values provided" -Level Info return $Arguments } catch { Write-Log "Failed to parse PolicyValues array string: $($_.Exception.Message)" -Level Error Write-Log "Expected format: '[\"value1\",\"value2\",\"value3\"]'" -Level Warning return @() } } else { # Not JSON format - use PolicyValuesArray directly as positional arguments Write-Log "Using positional arguments format (non-JSON): $($PolicyValuesArray.Count) values provided" -Level Info return $PolicyValuesArray } } # Parse PolicyValues array string to get individual arguments $Arguments = Parse-PolicyValuesArray -ArrayString $PolicyValues function Save-SecpolChanges { param([hashtable]$PolicyData) $exportPath = Join-Path $PSScriptRoot "secpol_export.inf" $modifiedPath = Join-Path $PSScriptRoot "secpol_modified.inf" try { Write-ProgressLog "Saving changes to security policy database..." # Write modified policy Write-InfFile -Data $PolicyData -Path $modifiedPath Write-Log "Modified policy written to: $modifiedPath" -Level 'DEBUG' # Apply the policy Write-Log "Applying security policy configuration..." -Level 'INFO' $seceditOutput = secedit /configure /db secedit.sdb /cfg $modifiedPath /areas SECURITYPOLICY 2>&1 if ($LASTEXITCODE -eq 0) { Write-Log "Security policy applied successfully" -Level 'SUCCESS' return $true } else { Write-Log "Secedit returned exit code: $LASTEXITCODE" -Level 'ERROR' Write-Log "Secedit output: $seceditOutput" -Level 'ERROR' return $false } } catch { Write-Log "Error saving security policy: $($_.Exception.Message)" -Level 'ERROR' return $false } } # Main execution Initialize-Script try { if ($PolicyDatabase.Count -eq 0) { Write-Log "Policy database is empty. Please populate the PolicyDatabase array." -Level 'WARNING' exit 0 } # Export current policy $exportPath = Join-Path $PSScriptRoot "secpol_export.inf" Write-Log "Exporting current security policy..." -Level 'INFO' secedit /export /cfg $exportPath /areas SECURITYPOLICY | Out-Null # Import current policy data $policyData = Import-InfFile -Path $exportPath if (-not $policyData) { Write-Log "Failed to import current security policy" -Level 'ERROR' exit 1 } # Process each policy with its corresponding argument for ($i = 0; $i -lt $PolicyDatabase.Count; $i++) { $policy = $PolicyDatabase[$i] $value = if ($i -lt $Arguments.Count) { $Arguments[$i] } else { $null } $script:ProcessedCount++ Set-SecpolRow -Name $policy.Name -KeyGroup $policy.KeyGroup -Key $policy.Key -Value $value -RegistryType $policy.RegistryType -PolicyData $policyData } # Save all changes if not in WhatIf mode and there were successful changes if (-not $WhatIf -and $script:SuccessCount -gt 0) { Save-SecpolChanges -PolicyData $policyData } } catch { Write-Log "Critical error in main execution: $($_.Exception.Message)" -Level 'ERROR' Write-Log "Stack trace: $($_.ScriptStackTrace)" -Level 'DEBUG' } finally { # Summary $duration = (Get-Date) - $script:StartTime Write-Log "========================================" -Level 'INFO' Write-Log "Execution Summary:" -Level 'INFO' Write-Log " Total Processed: $($script:ProcessedCount)" -Level 'INFO' Write-Log " Successful: $($script:SuccessCount)" -Level 'INFO' Write-Log " Failed: $($script:FailureCount)" -Level 'INFO' Write-Log " Skipped: $($script:SkippedCount)" -Level 'INFO' Write-Log " Duration: $($duration.TotalSeconds) seconds" -Level 'INFO' Write-Log "========================================" -Level 'INFO' }